
package com.huaweicloud.haydn.delivercore.agent.service;

import static com.huaweicloud.haydn.delivercore.agent.config.Constants.ACCESS_CODE;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.AUTO_REPORT;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_HOSTS;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_LINKNAME;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_PASSWORD;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_PORT;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_SELECTED;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_TYPE;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DB_USERNAME;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.EMPTY_FIELD;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.EMPTY_TABLE;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.FILETYPE_PROPERTIES;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.FILETYPE_ZIP;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.HAYDN_URL;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.PROJECT_ID;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.SCHEMA_SELECTED;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.SYS_URL;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TABLE_SELECTED;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_DB;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_EMPTY;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_FIELD;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_TABLE;

import com.alibaba.excel.util.StringUtils;
import com.huaweicloud.haydn.delivercore.agent.common.exception.BusinessException;
import com.huaweicloud.haydn.delivercore.agent.config.DatabaseContext;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.ConsistencyMonitoringService;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.DataExplorationService;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.DatabaseUtil;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.ImplementMonitoringService;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.impl.AgentExecUtil;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.impl.ConsistencyMonitoringServiceImpl;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.impl.TableRowsService;
import com.huaweicloud.haydn.delivercore.agent.utils.EncryptUtil;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.zip.ZipFile;

import javax.annotation.Resource;

/**
 * @since 2022-10-08
 */
@Component
@Slf4j
@PropertySource("file:application.properties")
public class ScheduledTask {

    @Autowired
    private AgentExecUtil excelUtil;

    @Autowired
    DatabaseContext databaseContext;

    @Autowired
    private DataExplorationService dataExplorationService;

    @Autowired
    private ImplementMonitoringService implementMonitoringService;

    @Resource(type = ConsistencyMonitoringServiceImpl.class)
    private ConsistencyMonitoringService consistencyMonitoringService;

    @Resource(type = TableRowsService.class)
    private ConsistencyMonitoringService tableRowsService;

    public static final int SIZE_LIMIT = 8 * 1024 * 1024;

    // use to record the number of error
    public static ThreadLocal<String> errors = new ThreadLocal<>();

    public void onceRun() {
        log.info("------once run start--------");
        // only run once,use to test
        genData();
        genTableData();
        genFieldData();
        log.info("------once run end--------");
    }

    @Scheduled(cron = "${db.cron}")
    @SneakyThrows
    public void genData() {
        genDataType(TYPE_DB);
    }

    @Scheduled(cron = "${table.cron}")
    @SneakyThrows
    public void genTableData() {
        genDataType(TYPE_TABLE);
    }

    @Scheduled(cron = "${field.cron}")
    @SneakyThrows
    public void genFieldData() {
        genDataType(TYPE_FIELD);
    }

    @Scheduled(cron = "${tablenaming.cron}")
    @SneakyThrows
    public void genTableNamingData() {
        log.info("SCHEDULED gen table naming monitor result now");
        implementMonitoringService.genTableNamingData();
    }

    @Scheduled(cron = "${tablefield.cron}")
    @SneakyThrows
    public void genAdditionalFieldData() {
        log.info("SCHEDULED gen additional field monitor result now");
        implementMonitoringService.genAdditionalFieldData();
    }

    @Scheduled(cron = "${report.cron}")
    @SneakyThrows
    public void reportData() {
        log.info("report now");
        try {
            Properties dataSourceConfig = new Properties();
            File file = new File(System.getProperty(SYS_URL) + File.separator + "application." + FILETYPE_PROPERTIES);
            try (InputStream in = Files.newInputStream(file.toPath())) {
                dataSourceConfig.load(in);
                String autoReport = dataSourceConfig.getProperty(AUTO_REPORT);
                String accessCode = dataSourceConfig.getProperty(ACCESS_CODE);
                String projectId = dataSourceConfig.getProperty(PROJECT_ID);
                String url = dataSourceConfig.getProperty(HAYDN_URL);
                if ("true".equals(autoReport)) {
                    File result = new File(System.getProperty(SYS_URL), "result." + FILETYPE_ZIP);
                    try (ZipFile zip = new ZipFile(result, StandardCharsets.UTF_8)) {
                        if (zip.size() < SIZE_LIMIT) {
                            dataExplorationService.parseAndUpload(accessCode, projectId, url);
                        } else {
                            dataExplorationService.obsUpload(accessCode, projectId, url);
                        }
                    }
                }
            }
        } catch (IOException | BusinessException e) {
            log.error(e.getMessage());
        }
    }

    public void genDataType(String type) {
        log.info("********Now start exploring " + type + " data************");
        errors.set("Type " + type);
        File file = new File(System.getProperty(SYS_URL) + File.separator + "application." + FILETYPE_PROPERTIES);
        try (InputStream in = Files.newInputStream(file.toPath())) {
            Properties dataSourceConfig = new Properties();
            dataSourceConfig.load(in);
            for (int i = 0; i < 100; i++) {
                if (!checkConfig(dataSourceConfig, i + 1)) {
                    break;
                }
                genDataInPool(i + 1, dataSourceConfig, type);
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        if (!errors.get().equals("Type " + type)) {
            log.error("--------" + errors.get() + " exist error---------");
        }
        log.info("********Now stop exploring " + type + " data************");
    }

    private void genDataInPool(int idx, Properties dataSourceConfig, String type) {
        log.info("exploring no." + idx + " " + type);
        try {
            String datalinkName = dataSourceConfig.getProperty(DB_LINKNAME + idx);
            String hosts = dataSourceConfig.getProperty(DB_HOSTS + idx);
            String port = dataSourceConfig.getProperty(DB_PORT + idx);
            String userName = dataSourceConfig.getProperty(DB_USERNAME + idx);
            String password = dataSourceConfig.getProperty(DB_PASSWORD + idx);
            if (password.startsWith("ENC(")) {
                password = EncryptUtil.decrypt(password);
            }
            String dbType = dataSourceConfig.getProperty(DB_TYPE + idx);
            DatabaseUtil dbUtil = databaseContext.getStrategy(dbType);
            String databaseName;
            if ("mysql".equals(dbType)) {
                databaseName = "information_schema";
            } else {
                databaseName = dataSourceConfig.getProperty(DB_SELECTED + idx);
            }
            dbUtil.init(hosts, port, databaseName, userName, password, dbType, "", "");
            String dbSelected = Optional.ofNullable(dataSourceConfig.getProperty(DB_SELECTED + idx)).orElse("");
            String schemaSelected = Optional.ofNullable(dataSourceConfig.getProperty(SCHEMA_SELECTED + idx)).orElse("");
            String tableSelected = Optional.ofNullable(dataSourceConfig.getProperty(TABLE_SELECTED + idx)).orElse("");
            String idxStr = String.valueOf(idx);
            switch (type) {
                case TYPE_DB:
                    excelUtil.genDbExcelDto(dbUtil, datalinkName, dbSelected, schemaSelected, idxStr);
                    excelUtil.genDbLog(dbUtil, datalinkName, idxStr);
                    break;
                case TYPE_TABLE:
                    excelUtil.genTableExcelDto(dbUtil, datalinkName, dbSelected, schemaSelected, tableSelected, idxStr);
                    break;
                case TYPE_FIELD:
                    excelUtil.genFieldExcelDto(dbUtil, datalinkName, dbSelected, schemaSelected, tableSelected, idxStr);
                    break;
                case TYPE_EMPTY:
                    Map<String, List<String>> fieldTableMap = new HashMap<>();
                    for (int i = 1; i < 6; i++) {
                        if (dataSourceConfig.getProperty(EMPTY_TABLE + idx + "." + i) == null
                            || dataSourceConfig.getProperty(EMPTY_FIELD + idx + "." + i) == null) {
                            break;
                        }
                        String emptyTable = dataSourceConfig.getProperty(EMPTY_TABLE + idx + "." + i);
                        String emptyFields = dataSourceConfig.getProperty(EMPTY_FIELD + idx + "." + i);
                        String[] fieldArr = emptyFields.split(",");
                        List<String> fields = Arrays.asList(fieldArr);
                        fieldTableMap.put(emptyTable, fields);
                    }
                    excelUtil.genEmptyRateExcelDto(dbUtil, datalinkName, dbSelected, schemaSelected, tableSelected,
                        idxStr, fieldTableMap);
                    break;
                default:
                    log.error("type is not support");
            }
        } catch (BusinessException e) {
            errors.set(errors.get() + " No." + idx);
            log.info(e.getMessage());
        }

    }

    private boolean checkConfig(Properties dataSourceConfig, int i) {
        boolean proBase = StringUtils.isNotBlank(dataSourceConfig.getProperty(DB_LINKNAME + i))
            || StringUtils.isNotBlank(dataSourceConfig.getProperty(DB_HOSTS + i))
            || StringUtils.isNotBlank(dataSourceConfig.getProperty(DB_PORT + i));

        boolean proSuper = StringUtils.isNotBlank(dataSourceConfig.getProperty(DB_USERNAME + i))
            || StringUtils.isNotBlank(dataSourceConfig.getProperty(DB_PASSWORD + i))
            || StringUtils.isNotBlank(dataSourceConfig.getProperty(DB_TYPE + i));

        return proBase || proSuper;
    }

    @Scheduled(cron = "${monitor.cron}")
    @SneakyThrows
    public void reportMonitorData() {
        log.info("report monitor data now");
        InputStream in = null;
        try {
            Properties dataSourceConfig = new Properties();
            File file = new File(System.getProperty(SYS_URL) + File.separator + "application." + FILETYPE_PROPERTIES);
            in = new FileInputStream(file);
            dataSourceConfig.load(in);
            String accessCode = dataSourceConfig.getProperty("monitor.access.code");
            String projectId = dataSourceConfig.getProperty("monitor.project.id");
            String url = dataSourceConfig.getProperty("monitor.haydn.url");
            implementMonitoringService.parseAndUpload(accessCode, projectId, url);
            log.info("report monitor data succeed!");
        } catch (IOException e) {
            log.error(e.getMessage());
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    @Scheduled(cron = "${consistency.tablefield.cron}")
    @SneakyThrows
    public void genTableFieldData() {
        log.info("SCHEDULED generate table field monitor consistency result now");
        consistencyMonitoringService.genTableFieldData();
    }

    @Scheduled(cron = "${consistency.tablerows.cron}")
    @SneakyThrows
    public void genTableRowsData() {
        log.info("SCHEDULED generate table rows monitor consistency result now");
        tableRowsService.genTableRowsData();
    }

    @Scheduled(cron = "${consistency.tablecontents.cron}")
    @SneakyThrows
    public void genTableContentsData() {
        log.info("SCHEDULED generate table contents monitor consistency result now");
        consistencyMonitoringService.genTableContentData();
    }

    @Scheduled(cron = "${consistency.monitor.cron}")
    @SneakyThrows
    public void reportMonitorConsistencyData() {
        consistencyMonitoringService.reportMonitorConsistencyData();
    }

}
